import {
  BufferAttribute,
  BufferGeometry,
  Color,
  DynamicDrawUsage,
  Matrix4,
  Mesh,
  MeshStandardMaterial,
  Vector3
} from '../../three.module.js';

function TubePainter() {

  const BUFFER_SIZE = 1000000 * 3;

  const positions = new BufferAttribute(new Float32Array(BUFFER_SIZE), 3);
  positions.usage = DynamicDrawUsage;

  const normals = new BufferAttribute(new Float32Array(BUFFER_SIZE), 3);
  normals.usage = DynamicDrawUsage;

  const colors = new BufferAttribute(new Float32Array(BUFFER_SIZE), 3);
  colors.usage = DynamicDrawUsage;

  const geometry = new BufferGeometry();
  geometry.setAttribute('position', positions);
  geometry.setAttribute('normal', normals);
  geometry.setAttribute('color', colors);
  geometry.drawRange.count = 0;

  const material = new MeshStandardMaterial({
    vertexColors: true
  });

  const mesh = new Mesh(geometry, material);
  mesh.frustumCulled = false;

  //

  function getPoints(size) {

    const PI2 = Math.PI * 2;

    const sides = 10;
    const array = [];
    const radius = 0.01 * size;

    for (let i = 0; i < sides; i++) {

      const angle = (i / sides) * PI2;
      array.push(new Vector3(Math.sin(angle) * radius, Math.cos(angle) * radius, 0));

    }

    return array;

  }

  //

  const vector1 = new Vector3();
  const vector2 = new Vector3();
  const vector3 = new Vector3();
  const vector4 = new Vector3();

  const color = new Color(0xffffff);
  let size = 1;

  function stroke(position1, position2, matrix1, matrix2) {

    if (position1.distanceToSquared(position2) === 0) return;

    let count = geometry.drawRange.count;

    const points = getPoints(size);

    for (let i = 0, il = points.length; i < il; i++) {

      const vertex1 = points[i];
      const vertex2 = points[(i + 1) % il];

      // positions

      vector1.copy(vertex1).applyMatrix4(matrix2).add(position2);
      vector2.copy(vertex2).applyMatrix4(matrix2).add(position2);
      vector3.copy(vertex2).applyMatrix4(matrix1).add(position1);
      vector4.copy(vertex1).applyMatrix4(matrix1).add(position1);

      vector1.toArray(positions.array, (count + 0) * 3);
      vector2.toArray(positions.array, (count + 1) * 3);
      vector4.toArray(positions.array, (count + 2) * 3);

      vector2.toArray(positions.array, (count + 3) * 3);
      vector3.toArray(positions.array, (count + 4) * 3);
      vector4.toArray(positions.array, (count + 5) * 3);

      // normals

      vector1.copy(vertex1).applyMatrix4(matrix2).normalize();
      vector2.copy(vertex2).applyMatrix4(matrix2).normalize();
      vector3.copy(vertex2).applyMatrix4(matrix1).normalize();
      vector4.copy(vertex1).applyMatrix4(matrix1).normalize();

      vector1.toArray(normals.array, (count + 0) * 3);
      vector2.toArray(normals.array, (count + 1) * 3);
      vector4.toArray(normals.array, (count + 2) * 3);

      vector2.toArray(normals.array, (count + 3) * 3);
      vector3.toArray(normals.array, (count + 4) * 3);
      vector4.toArray(normals.array, (count + 5) * 3);

      // colors

      color.toArray(colors.array, (count + 0) * 3);
      color.toArray(colors.array, (count + 1) * 3);
      color.toArray(colors.array, (count + 2) * 3);

      color.toArray(colors.array, (count + 3) * 3);
      color.toArray(colors.array, (count + 4) * 3);
      color.toArray(colors.array, (count + 5) * 3);

      count += 6;

    }

    geometry.drawRange.count = count;

  }

  //

  const up = new Vector3(0, 1, 0);

  const point1 = new Vector3();
  const point2 = new Vector3();

  const matrix1 = new Matrix4();
  const matrix2 = new Matrix4();

  function moveTo(position) {

    point1.copy(position);
    matrix1.lookAt(point2, point1, up);

    point2.copy(position);
    matrix2.copy(matrix1);

  }

  function lineTo(position) {

    point1.copy(position);
    matrix1.lookAt(point2, point1, up);

    stroke(point1, point2, matrix1, matrix2);

    point2.copy(point1);
    matrix2.copy(matrix1);

  }

  function setSize(value) {

    size = value;

  }

  //

  let count = 0;

  function update() {

    const start = count;
    const end = geometry.drawRange.count;

    if (start === end) return;

    positions.updateRange.offset = start * 3;
    positions.updateRange.count = (end - start) * 3;
    positions.needsUpdate = true;

    normals.updateRange.offset = start * 3;
    normals.updateRange.count = (end - start) * 3;
    normals.needsUpdate = true;

    colors.updateRange.offset = start * 3;
    colors.updateRange.count = (end - start) * 3;
    colors.needsUpdate = true;

    count = geometry.drawRange.count;

  }

  return {
    mesh: mesh,
    moveTo: moveTo,
    lineTo: lineTo,
    setSize: setSize,
    update: update
  };

}

export {TubePainter};
